﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TicketSeller
{
    class TicketsDB
    {
        private static TicketsDB _instance  = new TicketsDB( );

        private TicketsCollection tickets = new TicketsCollection( );

        private IDictionary<String, IList<Ticket>> replicated = new Dictionary<String, IList<Ticket>>();

        private TicketsDB() { }

        public static TicketsDB Instance
        {
            get
            {
                return _instance;
            }
        }

        /**
         * Just insert new ticket to database. 
        **/ 
        public TicketsDB insertTicket(Ticket ticket)
        {
            // As we insert new tickets only on server instantiation
            // there is no need to lock here, since 
            // no chance for cuncurrent data race here.
            tickets.Add( ticket);
            return _instance;
        }

        /**
         * Register new server for replication
         ***/ 
        public TicketsDB registerReplication( string serverName) 
        {
            IList<Ticket> serverTickets = null;
            lock (replicated)
            {
                replicated.TryGetValue(serverName, out serverTickets);
                if (serverTickets != null)
                {
                    replicated.Remove(serverName);
                }
                replicated.Add(serverName, new List<Ticket>());
            }
            return _instance;
        }


        /**
         * Insert replicated data from server "serverName" 
        **/
        public TicketsDB insertReplication(string serverName, Ticket ticket)
        {
            IList<Ticket> serverTickets = null;
            lock (replicated)
            {
                replicated.TryGetValue(serverName, out serverTickets);
                if (serverTickets == null)
                {
                    replicated.Add(serverName, serverTickets = new List<Ticket>());
                }

                serverTickets.Add(ticket);
            }
            return _instance;
        }

        /**
         * Once server "serverName" form alliance has come back or 
         * we discovered a failure we probably will need 
         * to invalidate stale data or at least renew it.
         * Hence this method is called to clean up old
         * irrelevant data.
         * 
        **/
        public TicketsDB invalidateReplica( string serverName)
        {
            lock (replicated)
            {
                replicated.Remove( serverName);
            }
            return _instance;
        }

        private RoutesCollection getRoutes( IList<Ticket> list, string source, string destination, string date) {
            RoutesCollection routes = new RoutesCollection();

            // Lookup direct flights
            var directCandidates = (from ticket in list
                                    where ticket.source == source &&
                                    ticket.destination == destination &&
                                    ticket.date == date
                                    select ticket).ToList<Ticket>();

        foreach (Ticket ticket in directCandidates)
        {
            routes.Add(new Route(ticket));
        }

            // Next we need to select indirect flights
            foreach (string server in replicated.Keys)
            {
                IList<Ticket> replica = null;
                replicated.TryGetValue(server, out replica);

                var indirectCandidates = from ticketSrc in list
                                            from ticketDst in replica
                                            where ticketSrc.source == source &&
                                            ticketSrc.destination != destination &&
                                            ticketSrc.date == date &&
                                            ticketDst.source == ticketSrc.destination &&
                                            ticketDst.destination == destination &&
                                            ticketDst.date == date
                                            select new TicketsCollection(ticketSrc, ticketDst);
                foreach (TicketsCollection ticketList in indirectCandidates)
                {
                    routes.Add(new Route(ticketList));
                }
            }

            return routes;
        }

        // Find routes for current server
        public RoutesCollection findFlightRoute( string source, string destination, string date)
        {
            lock (replicated)
            {
                return getRoutes(tickets, source, destination, date);
            }
        }

        // Find routes for other server
        public RoutesCollection findFlightRoute( string serverName, string source, string destination, string date)
        {
            RoutesCollection routes = new RoutesCollection();

            IList<Ticket> replicatedTickets = null;

            lock (replicated)
            {
                IList<Ticket> tmp = null;
                replicated.TryGetValue(serverName, out tmp);
                replicatedTickets = (from ticket in tmp select ticket).ToList<Ticket>();
                return getRoutes(replicatedTickets, source, destination, date);
            }
        }

        public TicketsCollection getTicketsCopy()
        {
            return new TicketsCollection( tickets);
        }

        public TicketsCollection getTicketsByServerName(string name)
        {
            IList<Ticket> result = null;
            replicated.TryGetValue(name, out result);
            if (result == null)
                return new TicketsCollection();
            return new TicketsCollection( result.ToArray( ));
        }

    }
}
